#include <string.h>

#include "drv_modbus.h"
#include "stm32f1xx_hal.h"

/* From drv_modbus_port.c -----------------------------------------------------*/
extern void ModbusRTU_Enable_Interrupts( void );
extern void ModbusRTU_Disable_Interrupts( void );
extern void ModbusRTU_EnableTimer( void );
extern void ModbusRTU_DisableTimer( void );
extern bool ModbusRTU_ReadByte( uint8_t *byte );
extern bool ModbusRTU_InitIdleCheckTimer( uint32_t idleUs );
extern void ModbusRTU_0x03_HandlerPort( uint8_t *buffer, uint8_t dataLen );
extern void ModbusRTU_0x06_HandlerPort( uint8_t *buffer, uint8_t dataLen );
extern bool ModbusRTU_InitUSARTPort( const ModbusRTU_InitTypedef * initTypedef );

#if (SYSTEM_SUPPORT == 1)

extern bool ModbusRTU_OSSourceInit( void );

#endif /* SYSTEM_SUPPORT */

/* Private function -----------------------------------------------------------*/
static void ModbusRTU_HandleReceiveData();
static void ModbusRTU_0x03_Handler( uint8_t * buffer, uint8_t dataLen );
static void ModbusRTU_0x06_Handler( uint8_t * buffer, uint8_t dataLen );

/* Private variable -----------------------------------------------------------*/
static uint8_t prvSlaveAddress = 0x01;

/**
  * @brief Modbus is used on no system mcu.
  */
#if (SYSTEM_SUPPORT == 0)

extern ModbusRTU_Event ModbusRTU_GetEvent( void )        __attribute__((weak, alias("ModbusRTU_Default_GetEvent")));
extern bool ModbusRTU_SendEvent( ModbusRTU_Event event ) __attribute__((weak, alias("ModbusRTU_Default_SendEvent")));

volatile static ModbusRTU_Event prvEvent = EVENT_NO;

ModbusRTU_Event ModbusRTU_Default_GetEvent( void )
{
	ModbusRTU_Disable_Interrupts();
	
	ModbusRTU_Event temp = prvEvent;
	prvEvent = EVENT_NO;
	
	ModbusRTU_Enable_Interrupts();
	
	return temp;
}

bool ModbusRTU_Default_SendEvent( ModbusRTU_Event evnet )
{
	prvEvent = evnet;
	return true;
}

/**
  * @brief Modbus is used on system os. those function need user supply implemention.
  */
#else

extern ModbusRTU_Event ModbusRTU_GetEvent( void );
extern bool ModbusRTU_SendEvent( ModbusRTU_Event event );

#endif

static ModbusRTU_State prvModbusState = STATE_INIT_FAIL;
static uint8_t prvRecvBuffer[256];
volatile uint8_t prvDataLen = 0;

/**
  * @brief Init USART and idle check timer.
	* 
  */
bool ModbusRTU_Init(const ModbusRTU_InitTypedef * initTypedef)
{
	bool ret = true;
	ret = ( ModbusRTU_InitUsart(initTypedef) 
	        && ModbusRTU_InitIdleCheckTimer(initTypedef->idleUs));
#if (SYSTEM_SUPPORT == 1)
	ret = ModbusRTU_OSSourceInit();
#endif
	return ret;
}


void ModbusRTU_EventLoopStart()
{
	while( 1 )
	{
		ModbusRTU_Event event = ModbusRTU_GetEvent();

		switch( event )
		{
			case EVENT_RECEIVED : 
			{
				ModbusRTU_HandleReceiveData();
			} break;
			
			default: break;
		}
		
		// HAL_Delay(50);
	}
}

static void ModbusRTU_HandleReceiveData()
{
	if( prvDataLen <  MODBUSRTU_DATA_MINIUM_LENGTH )
	{
		return;
	}
	
	// Executing corresponding functions based on function codes.
	uint8_t ucFuncCode = prvRecvBuffer[1];
	
	// Filter slave address.
	if( prvSlaveAddress == prvRecvBuffer[0] )
	{
		switch( ucFuncCode )
		{
			// Read register
			case 0x03:
			{
				ModbusRTU_0x03_Handler(prvRecvBuffer, prvDataLen);
			} break;
			
			// Read input switch status.
			case 0x06:
			{
				ModbusRTU_0x06_Handler(prvRecvBuffer, prvDataLen);
			} break;
 
			default: break;
		}
	}
	
	memset(prvRecvBuffer, 0, sizeof(prvRecvBuffer));
	prvDataLen = 0;
}

void ModbusRTU_UsartReceiveHandler( void )
{
	// 1. Change modbus state
	prvModbusState = STATE_RX;
	
	// 2. Start idle check timer
	ModbusRTU_EnableTimer();
	
	// 3. Read one byte
	uint8_t byte;
	ModbusRTU_ReadByte(&byte);
	
	// 4. Add data to buffer
	prvRecvBuffer[prvDataLen++] = byte;
}


void ModbusRTU_TimerExpiredHandler( void )
{
	//1. Change modbus state to IDLE
	prvModbusState = STATE_IDLE;
	
	//2. Send event to eventloop
	ModbusRTU_SendEvent( EVENT_RECEIVED );
}

void ModbusRU_SetSlaveAddress( uint8_t slaveAddress )
{
	prvSlaveAddress = slaveAddress;
}

/**
  * @brief Check the message of function code 0x03, including data
  *        length, CRC and call the user processing function.
  *
  * @param[In] buffer  : The data receive from serial port. 
  * @param[In] dataLen : data's len.
  */
void ModbusRTU_0x03_Handler( uint8_t * buffer, uint8_t dataLen )
{
	uint16_t registerCount = 0;
	uint16_t crcCode = 0;
	uint16_t temp = 0;
	
	// 1. Check data len
	if( dataLen != 8 )
	{
		return;
	}
	
	// 2. Check register count,  the 0x03 function code limits the number of registers
  //    that can be read to a maximum of 125.
	registerCount = buffer[4];
	registerCount = ( registerCount << 8 ) | buffer[5];
	if( registerCount > 125 )
	{
		return;
	}
	
	// 3. CRC check
	temp    = buffer[7];
  temp    = ( temp << 8 ) | buffer[6];	
	crcCode = ModbusRTU_CRC16( buffer, 6 );
	if( crcCode != temp )
	{
		return;
	}
	
	ModbusRTU_0x03_HandlerPort( buffer, dataLen );
}

/**
  * @brief Check the message of function code 0x06, including data
  *        length, CRC and call the user processing function.
  *
  * @param[In] buffer  : The data receive from serial port. 
  * @param[In] dataLen : data's len.
  */
void ModbusRTU_0x06_Handler( uint8_t * buffer, uint8_t dataLen )
{
	uint16_t crcCode = 0;
	uint16_t temp = 0;
	
	// 1. Check fix length.
	if( dataLen != 8 )
	{
		return;
	}
	
	// 1. Check CRC.
	temp    = buffer[7];
  temp    = ( temp << 8 ) | buffer[6];	
	crcCode = ModbusRTU_CRC16( buffer, 6 );
	if( crcCode != temp )
	{
		return;
	}
	
	// 2. Call User's process
	ModbusRTU_0x06_HandlerPort( buffer, dataLen );
}

 
/**
  * @brief Construct an error frame for the user to return.
  *
  * @param[In]  funcCode
  * @param[In]  errCode : @see ModbusRTU_ErrorCode.
  * @param[Out] buffer  : Error frame.
  * @param[Out] dataLen : Frame length.
  */
void ModbusRTU_BuildIllegalResponse(uint8_t funcCode, uint8_t errCode, uint8_t *buffer, uint16_t *dataLen )
{
	uint8_t cnter    = 0;
	uint16_t crcCode = 0;
	
	buffer[cnter++] = prvSlaveAddress;
	buffer[cnter++] = 0x80 + funcCode;
	buffer[cnter++] = errCode;
	
	crcCode = ModbusRTU_CRC16( buffer, cnter );
	buffer[cnter++] = crcCode & 0xFF;
	buffer[cnter++] = ( crcCode >> 8 ) & 0xFF;
}